package io.knifer.gokexceltask.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import io.knifer.gokexceltask.dao.ClasseDAO;
import io.knifer.gokexceltask.dao.FacultiesDAO;
import io.knifer.gokexceltask.dao.MajorDAO;
import io.knifer.gokexceltask.dao.StudentDAO;
import io.knifer.gokexceltask.excel.converter.IdCardNumberEncryptConverter;
import io.knifer.gokexceltask.excel.validator.StudentDataValidator;
import io.knifer.gokexceltask.exception.InvalidStudentDataException;
import io.knifer.gokexceltask.pojo.domain.*;
import io.knifer.gokexceltask.utils.AESUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import java.security.InvalidKeyException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

@Slf4j
public class StudentDataListener implements ReadListener<Student> {
    private final StudentDAO studentDAO;
    private final ClasseDAO classeDAO;
    private final FacultiesDAO facultiesDAO;
    private final MajorDAO majorDAO;
    private final List<Student> dataList;
    private final StudentDataValidator validator;
    private final Map<String, List<String>> validateMsg;
    private final boolean allowMiscDataAutoInsert;
    public final static int MAX_SIZE = 800;
    private final long startRowIndex;
    private static volatile boolean ending = false;    // 当最新线程发现自己处于文件末尾时，置此项为true

    public StudentDataListener(StudentDAO studentDAO, ClasseDAO classeDAO, FacultiesDAO facultiesDAO, MajorDAO majorDAO, StudentDataValidator validator, Map<String, List<String>> validateMsg, boolean allowMiscDataAutoInsert, long startRowIndex){
        this.studentDAO = studentDAO;
        this.classeDAO = classeDAO;
        this.facultiesDAO = facultiesDAO;
        this.majorDAO = majorDAO;
        this.dataList = ListUtils.newArrayListWithExpectedSize(MAX_SIZE);
        this.validator = validator;
        this.validateMsg = validateMsg;
        this.allowMiscDataAutoInsert = allowMiscDataAutoInsert;
        this.startRowIndex = startRowIndex;
    }

    @Override
    @Transactional
    public void invoke(Student student, AnalysisContext analysisContext) {
        // 校验学生数据合法，再添加
        try {
            validator.validate(student, allowMiscDataAutoInsert);
        } catch (InvalidStudentDataException e) {
            String rowNum = analysisContext.readRowHolder().getRowIndex().toString();
            List<String> errorMsg = validateMsg.getOrDefault(rowNum, new CopyOnWriteArrayList<>());

            errorMsg.addAll(e.getErrorMsg());
            validateMsg.put(rowNum, errorMsg);

            return;
        }

        dataList.add(student);
        if (dataList.size() == MAX_SIZE){
            flush();
            log.info("持久化数据");
            throw new ExcelAnalysisStopException();
        }
    }

    @Override
    @Transactional
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        if (analysisContext.readRowHolder().getRowIndex() <= startRowIndex){
            // 如果进入这个if，说明本次没有进行任何读取，也就是说目前已经到达了文件末尾
            ending = true;
        }
        flush();
        log.info("数据读取完成--{}|{}", startRowIndex, analysisContext.readRowHolder().getRowIndex());
    }

    private void flush(){
        dataList.forEach(this::prepareStudent);
        studentDAO.saveAll(dataList);
        dataList.clear();
    }

    private void prepareStudent(Student student){
        // 获取各项参数，其中对应有外键的实体如果不存在，就先行插入，然后再取ID
        String idCardNumber = null;
        Major major;
        Classe classe;
        Faculties faculties;
        String majorName = student.getMajor().getName();
        String classeName = student.getClasse().getName();
        String facultiesName = student.getFaculties().getName();

        major = majorDAO.findFirstByName(majorName)
                .orElseGet(() -> majorDAO.save(new Major(majorName)));
        classe = classeDAO.findFirstByName(classeName)
                .orElseGet(() -> classeDAO.save(new Classe(classeName)));
        faculties = facultiesDAO.findFirstByName(facultiesName)
                .orElseGet(() -> facultiesDAO.save(new Faculties(facultiesName)));

        try {
            idCardNumber = AESUtils.decryptBase64ToPlainText(
                    student.getIdCardNumber(),
                    IdCardNumberEncryptConverter.KEY
            );
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        student.setUser(new User(
                null,
                student.getStudentId(),
                StringUtils.substring(idCardNumber, idCardNumber.length() - 6),
                null,
                null,
                null
        ));
        student.setMajor(major);
        student.setClasse(classe);
        student.setFaculties(faculties);
    }

    public static boolean needMore(){
        return !ending;
    }
}
